#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "fs.h"

// 全局变量
byte *vdisk;
USEROPEN open_list[MAXOPENFILE];   // 用户打开文件表数组
int curindex;
char curdir[64];
char fname[80] = "";

void init() {
    vdisk = (byte *) calloc(1, SIZE);
    for (int i = 1; i < MAXOPENFILE; i++)
        open_list[i].topenfile = 0;
}

void fcbcpy(FCB *dst, const FCB *src) {
    strcpy(dst->filename, src->filename);
    strcpy(dst->extname, src->extname);
    dst->flag = src->flag;
    dst->time = src->time;
    dst->date = src->date;
    dst->start = src->start;
    dst->length = src->length;
    dst->free = src->free;
}

dbyte get_free_block() {
    FAT *fat = FAT1_ADDR(vdisk);
    for (int i = 0; i < SIZE / BLOCKSIZE; ++i) {
        if (FREE == fat[i].id)
            return i;
    }
    return END;
}

int get_free_open_list() {
    for (int i = 0; i < MAXOPENFILE; ++i) {
        if (0 == open_list[i].topenfile) {
            open_list[i].topenfile = 1;
            return i;
        }
    }
    return -1;
}

void startup(char *fs) {
    if (NULL == fs)
        fs = "";
    strcpy(fname, fs);
    FILE *fp = fopen(fname, "r");
    if (fp != NULL) {
        fread(vdisk, SIZE, 1, fp);
        fclose(fp);
        printf("已经载入文件系统: %s\n", fname);
    } else {
        printf("没有已存在的文件系统，准备格式化……\n");
        format();
        printf("文件系统格式化成功！\n");
    }
    FCB *root = ROOT_ADDR(vdisk);
    fcbcpy(&(open_list[0].fcb), root);
    strcpy(open_list[0].dir, "/");
    strcpy(curdir, "/");
    open_list[0].dirno = 5; // 根目录区起始块为5
    open_list[0].diroff = 0; // 是目录第1项
    open_list[0].parent = 0;
    open_list[0].ptr = 0;
    open_list[0].fcbstate = 0; // 未修改FCB
    open_list[0].topenfile = 1; // 目录已经被打开，此表项被占用
    curindex = 0; // 目前目录在第0处
}

void format() {
    FAT *fat = FAT1_ADDR(vdisk);
    META *meta = META_ADDR(vdisk);
    FCB *root = ROOT_ADDR(vdisk);

    // 设置启动块元数据
    strcpy(meta->info, "☭ A FAT16 File System ☭");
    strcpy(meta->oem, "By wlf, ...");  // FIXME: 这边加上名字
    meta->sector_size = SECTORSIZE;
    meta->block_num = SIZE / BLOCKSIZE;
    meta->block_size = BLOCKSIZE;
    meta->maxopenfile = MAXOPENFILE;
    meta->root = ROOT_START_BLOCK;
    meta->startblock = (byte *) root;

    // 为FAT设置标记
    for (int i = 0; i < 7; i++)  // 表示前7块均被占用
        fat[i].id = END;

    fat[5].id = 6; // 第5、6块均为跟目录区，此处6为5的后继
    for (int i = 7; i < SIZE / BLOCKSIZE; i++)
        fat[i].id = FREE;

    // 同步修改FAT2
    memcpy(FAT2_ADDR(vdisk), FAT1_ADDR(vdisk), FAT_SIZE * BLOCKSIZE);

    // 创建 . 目录项
    time_t now = time(NULL);
    struct tm *now_time = localtime(&now);
    strcpy(root[0].filename, ".");
    strcpy(root[0].extname, "");
    root[0].flag = 0;
    root[0].time = BUILD_TIME(now_time);
    root[0].date = BUILD_DATE(now_time);
    root[0].start = ROOT_START_BLOCK;
    root[0].length = 2 * sizeof(FCB);
    root[0].free = 1;

    // 创建 .. 目录项
    strcpy(root[1].filename, "..");
    strcpy(root[1].extname, "");
    root[1].flag = 0;
    root[1].time = BUILD_TIME(now_time);
    root[1].date = BUILD_DATE(now_time);
    root[1].start = ROOT_START_BLOCK;
    root[1].length = 2 * sizeof(FCB);
    root[1].free = 1;

    // 写入文件
    printf("#> 你想保存到哪里？\n#> ");
    scanf("%s", fname);
    FILE *fp = fopen(fname, "w");
    fwrite(vdisk, SIZE, 1, fp);
    fclose(fp);
    printf("保存成功！\n");
}

int cd(char *dirs) {
    char *dir = NULL;
    int index;
    if (NULL == dirs) {
        printf("缺少参数\n");
        return -1;
    }
    if (1 == open_list[curindex].fcb.flag) {
        printf("你现在打开的是文件，不能使用cd命令！\n");
        return -1;
    }
    // 关掉所有目录，从根目录开始
    if ('/' == dirs[0] || '\\' == dirs[0]) {
        while (curindex)
            curindex = close(curindex);
    }
    dir = strtok(dirs, PATH_SPLIT);
    // 从第一段开始
    while (dir != NULL) {
        if (0 == strcmp(dir, ".")) { // 当前目录
            // 什么都不用做
        } else if (0 == strcmp(dir, "..")) { // 上级目录
            if (curindex != 0)
                curindex = close(curindex); // 关掉上级目录
        } else { // 具体目录
            index = open(dir, 0);
            if (-1 == index) {
                return -1;
            }
        }
        dir = strtok(NULL, PATH_SPLIT);
    }
    return 0;
}

int open(char *name, int type) {
    char *filename = strtok(name, FNAME_SPLIT);
    char extname[3] = "";
    char *tmp = strtok(NULL, FNAME_SPLIT);
    int read_byte;
    byte *buf = (byte *) malloc(sizeof(byte) * MAX_FILE_SIZE);
    FCB *fcb = (FCB *) buf;
    if (tmp != NULL) {
        strcpy(extname, tmp);
    }
    for (int i = 0; i < MAXOPENFILE; ++i) {
        if (0 == strcmp(open_list[i].fcb.filename, filename)
            && 0 == strcmp(open_list[i].fcb.extname, extname)) {
            printf("文件 %s 已经打开！\n", filename);
            return -1;
        }
    }

    // 把当前目录读到buf中（现在若想打开文件则目前打开项必定为目录文件）
    open_list[curindex].ptr = 0;
    read_byte = do_read(curindex, buf, open_list[curindex].fcb.length);

    // 检查目录项FCB是否有该文件
    int i;
    for (i = 0; i < read_byte / sizeof(FCB); ++i, ++fcb) {
        if (0 == strcmp(fcb->filename, filename)
            && 0 == strcmp(fcb->extname, extname)) {
            break;
        }
    }
    if (i == read_byte / sizeof(FCB)) {
        printf("错误，没有找到 %s ！\n", filename);
        return -1;
    }

    // 打开类型检查
    if (0 == type && fcb->flag != 0) {
        printf("%s.%s 不是目录!\n", filename, extname);
        return -1;
    }

    // 检查能否打开文件
    int available = get_free_open_list();
    if (-1 == available) {
        printf("已打开的文件数量超过限制\n");
        return -1;
    }

    // 获取USEROPEN后完成配置
    fcbcpy(&(open_list[available].fcb), fcb);
    open_list[available].dirno = open_list[available].fcb.start;
    open_list[available].diroff = i;
    strcpy(open_list[available].dir, open_list[curindex].dir);
    strcat(open_list[available].dir, filename);
    if (0 == fcb->flag)
        strcat(open_list[available].dir, "/");
    open_list[available].parent = curindex;
    open_list[available].ptr = 0;
    open_list[available].fcbstate = 0;
    open_list[available].topenfile = 1;
    strcpy(curdir, open_list[available].dir);
    return available;
}

int do_read(int i, byte *msg, int len) {
    int tmp = len;
    byte *buf = (byte *) malloc(BLOCKSIZE * sizeof(byte));
    int offset = open_list[i].ptr;
    int block = open_list[i].fcb.start;
    FAT *fat = FAT1_ADDR(vdisk) + block;
    byte *block_p;

    // 读写指针大于BLOCKSIZE代表目前要读写的位置不在当前块，需要重新寻找block号块
    while (offset >= BLOCKSIZE) {
        offset -= BLOCKSIZE;
        block = fat->id;
        if (END == block) {
            printf("找不到该块用于读取！\n");
            return -1;
        }
        fat = FAT1_ADDR(vdisk) + block;
    }

    block_p = (byte *) (vdisk + BLOCKSIZE * block);
    // 把文件内容读入buf
    memcpy(buf, block_p, BLOCKSIZE);
    while (len > 0) {
        // 读的内容仅限于block号盘块
        if (offset + len < BLOCKSIZE) {
            memcpy(msg, buf + offset, len); // 直接把要读取的内容从buf中拷贝给msg
            open_list[i].ptr += len; // 读取指针后移
            len = 0; // 退出循环
        } else { // 一个盘块不够读，还要读后续盘块
            memcpy(msg, buf + offset, BLOCKSIZE - offset); // 该块所有剩下的内容均读取
            msg += (BLOCKSIZE - offset); // 目的地指针后移
            len -= (BLOCKSIZE - offset); // 待读取字节数减少
            offset = 0; // 后续盘块从0开始读取

            // 查找下一块
            block = fat->id;
            if (block == END) {
                printf("试图读取的文件大小超过了文件大小！\n");
                break;
            }
            fat = FAT1_ADDR(vdisk) + block;
            block_p = (byte *) (vdisk + BLOCKSIZE * block);
            memcpy(buf, block_p, BLOCKSIZE); // 更新buf
        }
    }
    free(buf);
    return tmp - len;
}

int close(int i) {
    FCB *fcb;
    int pi = open_list[i].parent;
    if (i < 0) {
        printf("错误：找不到文件！\n");
        return -1;
    } else if (i >= MAXOPENFILE) {
        printf("错误：你打开了太多文件，最多允许打开 10 个文件/目录！\n");
        return -1;
    }

    // 修改了文件的FCB，需要写回
    if (1 == open_list[i].fcbstate) {
        fcb = NEW(FCB);
        fcbcpy(fcb, &(open_list[i].fcb));
        open_list[pi].ptr = (int) sizeof(FCB) * open_list[i].diroff;
        do_write(pi, (byte *) fcb, sizeof(FCB), 2);
        free(fcb);
        open_list[i].fcbstate = 0;
    }

    // 更新当前目录
    strcpy(curdir, open_list[pi].dir);

    // 归还open_list数组项
    strcpy(open_list[i].fcb.filename, "");
    strcpy(open_list[i].fcb.extname, "");
    open_list[i].topenfile = 0; // 该项置空
    return pi;
}

int do_write(int i, byte *msg, int len, int type) {
    int block = open_list[i].fcb.start;
    FAT *fat = FAT1_ADDR(vdisk) + block;
    byte *buf = (byte *) malloc(BLOCKSIZE * sizeof(byte));
    // 预处理要写入的文件
    switch (type) {
        case 0: // 从头覆盖写入
            open_list[i].ptr = 0;
            open_list[i].fcb.length = 0;
            break;
        case 1: // 从文件的ptr指向之后开始写
            if (1 == open_list[i].fcb.flag && open_list[i].fcb.length != 0)
                open_list[i].ptr--;
            break;
        case 2: // 从文件结尾开始写
            if (1 == open_list[i].fcb.flag)
                open_list[i].ptr = open_list[i].fcb.length;
            else if (1 == open_list[i].fcb.flag && open_list[i].fcb.length != 0)
                open_list[i].ptr = open_list[i].fcb.length - 1;
            break;
        default:
            return -1;
    }
    int offset = open_list[i].ptr;
    // offset 大于 BLOCKSIZE 说明指向不在第1个盘块，需要找到那个盘块
    while (offset >= BLOCKSIZE) {
        block = fat->id;
        if (END == block) {
            block = get_free_block();
            if (END == block) {
                printf("错误：盘块不足！\n");
                return -1;
            } else {
                // 分配新盘块，登记到FAT1中
                fat->id = block;
                fat = FAT1_ADDR(vdisk) + block;
                fat->id = END;
            }
        }
        fat = FAT1_ADDR(vdisk) + block;
        offset -= BLOCKSIZE; // 循环变量更新
    }
    byte *block_p = (byte *) (vdisk + BLOCKSIZE * block);
    int tmp = 0; // 已写入字节
    while (len > tmp) {
        // 读取block内的内容到buf
        memcpy(buf, block_p, BLOCKSIZE);
        // msg写入buf
        for (; offset < BLOCKSIZE; offset++) {
            *(buf + offset) = *msg;
            msg++;
            tmp++;
            if (tmp == len)
                break;
        }
        // buf写入block
        memcpy(block_p, buf, BLOCKSIZE);
        // buf被写满，分配新block
        if (offset == BLOCKSIZE && len != tmp) {
            offset = 0;
            block = fat->id;
            if (END == block) {
                block = get_free_block();
                if (END == block) {
                    printf("错误：盘块不足！\n");
                    return -1;
                } else {
                    // 分配新盘块，登记到FAT1中
                    fat->id = block;
                    fat = FAT1_ADDR(vdisk) + block;
                    fat->id = END;
                    block_p = (byte *) (vdisk + BLOCKSIZE * block);
                }
            } else {
                fat = FAT1_ADDR(vdisk) + block;
                block_p = (byte *) (vdisk + BLOCKSIZE * block);
            }
        }
    }
    open_list[i].ptr += len; // 读写指针后移代表写完
    if (open_list[i].ptr > open_list[i].fcb.length)
        open_list[i].fcb.length = open_list[i].ptr;
    free(buf);
    int j = block;
    FAT *fatty = FAT1_ADDR(vdisk);
    while (fatty[j].id != END) {
        int next = fatty[j].id;
        fatty[j].id = FREE;
        j = next;
    }
    fatty[block].id = END;
    // 同步修改FAT2
    memcpy(FAT2_ADDR(vdisk), FAT1_ADDR(vdisk), FAT_SIZE * BLOCKSIZE);
    return 0;
}

void ls() {
    if (open_list[curindex].fcb.flag == 1) {
        printf("在数据文件里不能使用ls\n");
        return;
    }
    byte *buf = (byte *) malloc(sizeof(byte) * MAX_FILE_SIZE);//char buf[MAX_FILE_SIZE];

    open_list[curindex].ptr = 0;
    do_read(curindex, buf, open_list[curindex].fcb.length);//do_read(curindex, open_list[curindex].length, buf);

    FCB *fcbPtr = (FCB *) buf;
    printf("%15s%11s%10s%16s%17s\n", "名称", "大小", "类型", "修改日期", "修改时间");
    //printf("名称\t\t大小\t类型\t修改日期\t修改时间\n");
    for (int i = 0; i < (int) (open_list[curindex].fcb.length / sizeof(FCB)); i++) {
        if (fcbPtr->free == 1) {
            //目录文件
            //同理,年份占7位,月份占4位,日期占5位
            //小时占5位,分钟占6位,秒占5位
            if (fcbPtr->flag == 0) {
                printf("%12s\t%4dB\t<DIR>\t%4d/%02d/%02d\t%02d:%02d:%02d\n",
                       fcbPtr->filename, fcbPtr->length,
                       (fcbPtr->date >> 9) + 2000,
                       (fcbPtr->date >> 5) & 0x000f,
                       (fcbPtr->date) & 0x001f,
                       (fcbPtr->time >> 11),
                       (fcbPtr->time >> 5) & 0x003f,
                       (fcbPtr->time) & 0x001f * 2);
            } else {
                // 普通文件length - 2 是因为末尾有/n和/0两个字符
                unsigned int length = fcbPtr->length;
                if (length != 0)length -= 2;
                printf("%8s.%3s\t%4dB\t<File>\t%4d/%02d/%02d\t%02d:%02d:%02d\n",
                       fcbPtr->filename,
                       fcbPtr->extname,
                       length,
                       (fcbPtr->date >> 9) + 2000,
                       (fcbPtr->date >> 5) & 0x000f,
                       (fcbPtr->date) & 0x001f,
                       (fcbPtr->time >> 11),
                       (fcbPtr->time >> 5) & 0x003f,
                       (fcbPtr->time) & 0x001f * 2);
            }
        }
        fcbPtr++;
    }
}

int create(char *filename) {
    char *fileName = strtok(filename, ".");
    char *extname = strtok(NULL, ".");
    if (strcmp(fileName, "") == 0) {
        printf("请输入文件名\n");
        return -1;
    }
    if (!extname) {
        printf("请输入后缀名\n");
        return -1;
    }
    if (open_list[curindex].fcb.flag == 1) {
        printf("数据文件下不允许使用create\n");
        return -1;
    }
    //读取currfd对应的文件
    open_list[curindex].ptr = 0;
    byte *buf = (byte *) malloc(sizeof(byte) * MAX_FILE_SIZE);
    do_read(curindex, buf, open_list[curindex].fcb.length);
    int i;
    FCB *fcbPtr = (FCB *) (buf);
    //看看有没有重名文件
    for (i = 0; i < (int) (open_list[curindex].fcb.length / sizeof(FCB)); i++, fcbPtr++) {
        if (strcmp(fcbPtr->filename, fileName) == 0 && strcmp(fcbPtr->extname, extname) == 0) {
            printf("已有同名文件存在!\n");//cout << "已有同名文件存在!" << endl;
            return -1;
        }
    }
    //寻找空的fcb块
    fcbPtr = (FCB *) (buf);
    FCB *debug = (FCB *) (buf);
    for (i = 0; i < (int) (open_list[curindex].fcb.length / sizeof(FCB)); i++, fcbPtr++) {
        if (fcbPtr->free == 0)break;
    }
    //取一个盘块
    int blockNum = get_free_block();
    if (blockNum == -1) {
        return -1;
    }
    FAT *fat1 = (FAT *) (vdisk + BLOCKSIZE);  //指向分配表
    fat1[blockNum].id = END;
    FAT *fat2 = (FAT *) (vdisk + BLOCKSIZE * 3);  //指向备份分配表
    memcmp(fat2, fat1, BLOCKSIZE * 2);   //进行前blocksize字节比较
    //往fcb里写信息
    strcpy(fcbPtr->filename, filename);
    strcpy(fcbPtr->extname, extname);
    time_t rawTime = time(NULL);
    struct tm *time = localtime(&rawTime);   //利用库中的tm结构体
    fcbPtr->date = (time->tm_year - 100) * 512 + (time->tm_mon + 1) * 32 + (time->tm_mday);
    fcbPtr->time = (time->tm_hour) * 2048 + (time->tm_min) * 32 + (time->tm_sec) / 2;
    fcbPtr->start = blockNum;
    fcbPtr->free = 1;
    fcbPtr->length = 0;
    fcbPtr->flag = 1;
    open_list[curindex].ptr = i * sizeof(FCB);
    do_write(curindex, (byte *) fcbPtr, sizeof(FCB), 1);
    //修改当前目录文件的.目录项的长度
    fcbPtr = (FCB *) buf;
    fcbPtr->length = open_list[curindex].fcb.length;
    open_list[curindex].ptr = 0;
    do_write(curindex, (byte *) fcbPtr, sizeof(FCB), 1);
    open_list[curindex].fcbstate = 1;
    return 0;
}

void rm(char *filename) {

    char *fileName = strtok(filename, ".");
    char *extname = strtok(NULL, ".");
    if (!extname) {
        printf("请输入后缀名\n");
        return;
    }
    if (strcmp(extname, "di") == 0) {
        printf("不能删除目录项\n");
        return;
    }
    byte *buf = (byte *) malloc(sizeof(byte) * MAX_FILE_SIZE);
    open_list[curindex].ptr = 0;
    do_read(curindex, buf, open_list[curindex].fcb.length);
    int i;
    FCB *fcbPtr = (FCB *) buf;
    //寻找叫这个名字的文件目录项
    for (i = 0; i < (int) (open_list[curindex].fcb.length / sizeof(FCB)); i++, fcbPtr++) {  //查找文件
        if (strcmp(fcbPtr->filename, fileName) == 0 && strcmp(fcbPtr->extname, extname) == 0) {
            break;
        }
    }
    if (i == (int) (open_list[curindex].fcb.length / sizeof(FCB))) {
        printf("没有这个文件\n");
        return;
    }
    //清空这个目录项占据的FAT
    int blockNum = fcbPtr->start;
    FAT *fat1 = (FAT *) (vdisk + BLOCKSIZE);
    int next = 0;
    while (1) {
        next = fat1[blockNum].id;
        fat1[blockNum].id = FREE;
        if (next != END) {
            blockNum = next;
        } else {
            break;
        }
    }
    //备份fat2
    fat1 = (FAT *) (vdisk + BLOCKSIZE);
    FAT *fat2 = (FAT *) (vdisk + BLOCKSIZE * 3);
    memcpy(fat2, fat1, sizeof(FAT));
    //修改这个fcb为空
    fcbPtr->date = 0;
    fcbPtr->time = 0;
    fcbPtr->extname[0] = '\0';
    fcbPtr->filename[0] = '\0';
    fcbPtr->start = 0;
    fcbPtr->free = 0;
    fcbPtr->length = 0;
    //写到磁盘上去, 更新fcb内容为空
    open_list[curindex].ptr = i * sizeof(FCB);
    do_write(curindex, (byte *) fcbPtr, sizeof(FCB), 1);
    open_list[curindex].fcb.length -= sizeof(FCB);
    //更新.目录项的长度
    fcbPtr = (FCB *) buf;
    fcbPtr->length = open_list[curindex].fcb.length;
    open_list[curindex].ptr = 0;
    do_write(curindex, (byte *) fcbPtr, sizeof(FCB), 1);
    open_list[curindex].fcbstate = 1;
}